package com.witsoft.device.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.witsoft.device.dao.DeviceStatusTimeLineDao;
import com.witsoft.device.entity.DeviceEntity;
import com.witsoft.device.entity.DeviceStatusTimeLineEntity;
import com.witsoft.device.enums.MachineStatusEnum;
import com.witsoft.device.model.DeviceRealTimeSpent;
import com.witsoft.device.service.DeviceService;
import com.witsoft.device.service.DeviceStatusTimeLineService;
import com.witsoft.device.utils.DateUtil;
import com.witsoft.manager.MessageThreadManager;
import com.witsoft.rabbitMq.RabbitSender;
import com.witsoft.thingsboard.domain.DeviceStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class DeviceStatusTimeLineImplService extends ServiceImpl<DeviceStatusTimeLineDao, DeviceStatusTimeLineEntity> implements DeviceStatusTimeLineService{

    @Autowired
    private DeviceService deviceService;

    @Resource
    private DeviceStatusTimeLineDao deviceStatusTimeLineDao;

    @Autowired
    private RabbitSender rabbitSender;

    private Map<String, String> devices = null;

    @Resource
    private MessageThreadManager threadPoolManager;


    /**
     * @desc 计算实时的开机时长  2022.04.07 miki
     * @param deviceId
     * @return
     */
    @Override
    public Long getRealOpeningTimeCurrentDay(String deviceId) {
        DeviceStatusTimeLineEntity realOpeningTimeCurrentDayV1 = deviceStatusTimeLineDao.getRealOpeningTimeCurrentDayV1(deviceId);
        log.info("计算设备实时运行时长，当前设备的最近一次开机时间：{}", JSONObject.toJSONString(realOpeningTimeCurrentDayV1));
        if(ObjectUtils.isEmpty(realOpeningTimeCurrentDayV1)){
            return 0l;
        }

        Long leftSeconds = DateUtil.getLeftSeconds(realOpeningTimeCurrentDayV1.getStartTime(), new Date());
        return leftSeconds;
    }

    @Override
    public DeviceStatusTimeLineEntity getLastOneInfoByStatusYesterday(String deviceId, String status) {
        return deviceStatusTimeLineDao.getLastOneInfoByStatusYesterday(deviceId, status);
    }

    @Override
    public Long getSumRunningTimeByDayTime(String deviceId, String date) {
        return deviceStatusTimeLineDao.getSumRunningTimeByDayTime(deviceId, date);
    }

    @Override
    public Long getSumOpenningTimeByDayTime(String deviceId, String date) {
        return deviceStatusTimeLineDao.getSumOpenningTimeByDayTime(deviceId, date);
    }

    @Override
    public Long getSumAllRunningTimeDayOfTotalSpentIsNull() {

        List<DeviceRealTimeSpent> allRunningTime = deviceStatusTimeLineDao.getSumAllRunningTimeDayOfTotalSpentIsNull();
        Map<String, DeviceRealTimeSpent> runningTime = distinct(allRunningTime);

        Double sumOpen = 0d, sumRuning = 0d;

        //计算总的运行时长
        for (String key : runningTime.keySet()) {
            DeviceRealTimeSpent spent = runningTime.get(key);

            //校验后面是否存在已处理过的时许记录
            Integer num = deviceStatusTimeLineDao.checkIsExistSpentIsNullAfter(spent.getStartTime(), spent.getDeviceId(), MachineStatusEnum.RUNNING.getCodeStr());
            if(num <= 0){
                sumRuning += spent.getRealTimeSpent();
            }
        }

        return sumRuning.longValue();
    }


    @Override
    public Long getSumAllOpeningTimeDayOfTotalSpentIsNull() {
        List<DeviceRealTimeSpent> sumAllOpeningTime = deviceStatusTimeLineDao.getSumAllOpeningTimeDayOfTotalSpentIsNull();
        Map<String, DeviceRealTimeSpent> timeSpents = distinct(sumAllOpeningTime);

        Double sumOpen = 0d, sumRuning = 0d;
        //计算总的开机时长
        for (String key : timeSpents.keySet()) {
            DeviceRealTimeSpent spent = timeSpents.get(key);

            //校验后面是否存在已标记过（total_spent记录值）的时许记录，如果不存在，才计算当前的连续设备运行时长
            Integer num = deviceStatusTimeLineDao.checkIsExistSpentIsNullAfter(spent.getStartTime(), spent.getDeviceId(), MachineStatusEnum.TURNING.getCodeStr());
            if(num <= 0){
                sumOpen += spent.getRealTimeSpent();
            }
        }
        return sumOpen.longValue();
    }


    /**
     * @desc 去重数据
     * @param data
     */
    @Override
    public Map<String, DeviceRealTimeSpent> distinct(List<DeviceRealTimeSpent> data){

        Map<String, DeviceRealTimeSpent> map = new HashMap<>(data.size());
        data.forEach(deviceRealTimeSpent -> {
            String deviceId = deviceRealTimeSpent.getDeviceId();
            if(map.containsKey(deviceId)){
                DeviceRealTimeSpent old = map.get(deviceId);
                //优先取重复数据里面最新的
                if(old.getStartTime().before(deviceRealTimeSpent.getStartTime())){
                    map.put(deviceId, deviceRealTimeSpent);
                }
            }else{
                map.put(deviceId, deviceRealTimeSpent);
            }
        });

        return map;
    }


    @Override
    public Long getSumOpeningLastMonth(String deviceId) {
        return deviceStatusTimeLineDao.getSumOPenningMonth(deviceId);
    }


    /**
     * @desc 根据状态码获取当天的设备的最近一条记录
     * @author miki
     */
    @Override
    public DeviceStatusTimeLineEntity getLastOneInfoByStatus(String deviceId, String status) {

        return deviceStatusTimeLineDao.getLastOneInfoByStatus(deviceId, status);
    }


    @Override
    public Long getSumRunningTimeLastMonth(String deviceId) {
        //v2 优化查询总的运行时长问题   2021.11.08
        return deviceStatusTimeLineDao.getSumRunningMonthV2(deviceId);
        //v1
        //return deviceStatusTimeLineDao.getSumRunningMonth(deviceId);
    }


    /**
     * @desc 初始化内存中设备列表
     */
    @PostConstruct
    private void init(){
        //使用线程安全的hashMap
        devices = new ConcurrentHashMap<>(32);

        List<DeviceEntity> allList = deviceService.getAllList();
        allList.forEach(deviceEntity -> {
            devices.put(deviceEntity.getName(), deviceEntity.getId());
        });
    }


    @Override
    public void reportStatusNoSplitFlow(String obj){
        DeviceEntity device = null;

        try{
            DeviceStatus deviceStatus = JSONObject.parseObject(obj, DeviceStatus.class);
            //添加运行状态信息
            String deviceName = deviceStatus.getDeviceName();
            if(ObjectUtils.isEmpty(deviceName)){
                return;
            }

            //feature(2021.12.07): 增加异步mes转发
            threadPoolManager.addMsg(deviceStatus);


            String running = deviceStatus.getRunning();
            String machinesOnline = deviceStatus.getMachinesOnline();
            //设备开机状态
            MachineStatusEnum status = deviceStatus.getStatus();
            //设备运行状态
            MachineStatusEnum runningStatus = deviceStatus.getRunningStatus();

            //不存在，则创建
            if(!devices.containsKey(deviceName)){
                log.info("###########设备不存在：{}，执行创建操作", deviceName);
                device = addDevice(deviceStatus);
            }else{
                device = deviceService.getDeviceByName(deviceName);

                //fixed(2021.12.02): 解决并发操作，导致更新丢失的bug  --场景复现: 同时时间点（毫秒级），线程A先拿到库中设备状态2，线程B准备更新状态为1，B先执行更新，A后执行更新，此时A的状态是2，导致B更新的状态被覆盖丢失
                //解决方案：新增一个update实体，只更新变动的字段，不进行整体更新（这样状态字段就不会被覆盖）
                DeviceEntity update = new DeviceEntity();
                update.setId(device.getId());

                //设备状态，遥测数据 --更新（只更新running状态   2021.12.10）
                if(!ObjectUtils.isEmpty(runningStatus)){
                    //fixed(2021.11.11): 设备表中不能存在状态为3
                    update.setStatus(runningStatus.getCodeStr());
                }

                //fixed(2022.04.02): 解决设备生产数量为0的时候，被记录进去，导致每日凌晨统计前一日生产状况的时候，导致昨日生产数为0
                if(deviceStatus.getGoodQuantity().longValue() > 0){
                    update.setGoodCount(deviceStatus.getGoodQuantity().longValue());
                    update.setBadCount(deviceStatus.getBadQuantity().longValue());

                    //fixed(2022.01.11): 总生产数小于良品数  device.getGoodCount() --> update.getGoodCount();
                    update.setTotalCount(update.getGoodCount() + update.getBadCount());
                }else{
                    //fixed(2022.04.02): 记录异常情况的设备
                    log.info("设备生产数量统计异常,设备生产情况：{}", obj);
                }

                //v1 解决并发操作，导致更新丢失的bug
                //deviceService.saveOrUpdate(device);
                //v2
                deviceService.saveOrUpdate(update);
            }


            //第一步：处理开机状态时序记录 -- 对于事件不带running和machineOnline字段的，不处理到时序表中  2021.11.03
            if(!ObjectUtils.isEmpty(machinesOnline) || !ObjectUtils.isEmpty(running)){

                log.info("设备运行状态变化,上传数据：{}, ###########设备当前运行状态：{}，将要变更状态为：{}", obj, device.getStatus(), status);

                //初始化新记录
                DeviceStatusTimeLineEntity newEntity = new DeviceStatusTimeLineEntity();
                newEntity.setId(UUID.randomUUID().toString());
                newEntity.setDeviceId(device.getId());
                newEntity.setStartTime(deviceStatus.getCreateDate());

                //### 开机事件(优先级高于running, 所以if判断逻辑TURNING，STOPPING必须在 RUNNING和ERROR的前面 2022.04.07)：增加开机记录
                if(status == MachineStatusEnum.TURNING){
                    newEntity.setEventTime(deviceStatus.getCreateDate());
                    newEntity.setStatus(MachineStatusEnum.TURNING.getCodeStr());
                    this.save(newEntity);


                    //处理关机时长
                    updateLastStoppingEvent(deviceStatus, device);
                    //fixed（2021.11.11）：移除return  >>产线环境下：{"machinesOnline":"1","running":"0"} 开机和（运行或待机）两个状态码是同时一个事件传递的,需要向下接着处理running状态
                    //return;
                }


                //### 停机事件（优先级高于running）：更新最近的一次开机事件，并计算开机时间    --feature（2021.11.11）：产线环境下：{"machinesOnline":"0"} 关机状态码是单独传递的
                if(status == MachineStatusEnum.STOPPING){
                    newEntity.setEventTime(deviceStatus.getCreateDate());
                    newEntity.setStatus(MachineStatusEnum.STOPPING.getCodeStr());
                    this.save(newEntity);

                    //处理开机时长
                    updateLastTurningEvent(deviceStatus, device);

                    //fixed（2021.11.18）：如果停机之前，设备状态是运行，则更新运行时长
                    updateLastRunningEvent(deviceStatus, device);

                    //fixed（2021.11.18）：如果停机之前，设备状态待机，则更新待机时长
                    updateLastWaitingEvent(deviceStatus, device);
                    return;
                }



                //### 设备运行事件：更新最近的一次待机事件，并计算待机时间
                if(MachineStatusEnum.RUNNING.getCodeStr().equals(running)){

                    //fixed（2021.11.12）：重新初始化uuid
                    newEntity.setId(UUID.randomUUID().toString());

                    newEntity.setStatus(MachineStatusEnum.RUNNING.getCodeStr());
                    newEntity.setEventTime(deviceStatus.getCreateDate());
                    //新增时序记录
                    this.save(newEntity);

                    //更新今天最近的一设备待机事件记录
                    updateLastWaitingEvent(deviceStatus, device);
                    return;
                }


                //### 设备异常事件：新增时序记录， 并更新当天上一条最近的RUNNING记录

                //fixed(工利项目设备异常状态按待机（空闲）处理：)：设备没有异常状态，按空闲状态处理  2021.11.11
                if(MachineStatusEnum.ERROR.getCodeStr().equals(running)){
                    newEntity.setStatus(MachineStatusEnum.WAITING.getCodeStr());
                    newEntity.setEventTime(deviceStatus.getCreateDate());

                    this.save(newEntity);

                    //更新最近的运行事件结束时间点
                    updateLastRunningEvent(deviceStatus, device);
                }
            }

            //###################################################################################
            // 旧的逻辑代码，暂时保留，不使用！！！！！！！！！！！！！！！！！！！！！！！！！
        }catch(Exception e){
            log.error("设备状态上报异常：{}", e.getMessage());
        }
    }


    /**
     * @desc 更新最近一条停机事件的停机时长
     * @param deviceStatus
     * @param device
     */
    private void updateLastStoppingEvent(DeviceStatus deviceStatus, DeviceEntity device){

        //获取今天最近的一次关机事件记录
        DeviceStatusTimeLineEntity lastOneSTOPPING = getLastOneInfoByStatus(device.getId(), MachineStatusEnum.STOPPING.getCodeStr());

        //fixed（2021.11.11）：如果最近的stopping事件，已经标记了end_time，不需要重新再处理
        if(!ObjectUtils.isEmpty(lastOneSTOPPING) && ObjectUtils.isEmpty(lastOneSTOPPING.getEndTime())){
            //计算时间差
            Long leftSeconds = DateUtil.getLeftSeconds(lastOneSTOPPING.getStartTime(), deviceStatus.getCreateDate());
            lastOneSTOPPING.setTotalSpent(leftSeconds);

            //fixed（修复时序图展示缺失问题需要结束时间）：完善时序图结束时间  2021.11.10
            lastOneSTOPPING.setEndTime(deviceStatus.getCreateDate());
            lastOneSTOPPING.setEventTime(deviceStatus.getCreateDate());

            //更新差值
            this.saveOrUpdate(lastOneSTOPPING);
            log.info("###########设备开机，更新当天最近一次status为 4 的时序记录：{}，记录主键为：{}",device.getName(), JSONObject.toJSONString(lastOneSTOPPING));
        }

        //如果为空，今天认为异常，不处理
        //...
    }

    /**
     * @desc 更新最近一条运行事件的运行时长
     * @param deviceStatus
     * @param device
     */
    private void updateLastRunningEvent(DeviceStatus deviceStatus, DeviceEntity device){
        //获取今天最近的一设备运行事件记录
        DeviceStatusTimeLineEntity lastOneRUNNING = getLastOneInfoByStatus(device.getId(), MachineStatusEnum.RUNNING.getCodeStr());

        //fixed（2021.11.11）：如果最近的running事件，已经标记了end_time，不需要重新再处理
        if(ObjectUtils.isEmpty(lastOneRUNNING) || !ObjectUtils.isEmpty(lastOneRUNNING.getEndTime())){
            //如果为空，今天认为异常，不处理
            //...
            return;
        }

        //计算时间差
        Long leftSeconds = DateUtil.getLeftSeconds(lastOneRUNNING.getStartTime(), deviceStatus.getCreateDate());
        lastOneRUNNING.setTotalSpent(leftSeconds);


        //fixed（修复时序图展示缺失问题需要结束时间）：完善时序图结束时间  2021.11.10
        lastOneRUNNING.setEndTime(deviceStatus.getCreateDate());
        lastOneRUNNING.setEventTime(deviceStatus.getCreateDate());
        log.info("###########设备待机，更新当天最近一次status为 1 的时序记录：{}，记录主键为：{}",device.getName(), JSONObject.toJSONString(lastOneRUNNING));
        //更新差值
        this.saveOrUpdate(lastOneRUNNING);
    }

    /**
     * @desc 更新最近一条待机事件的待机时长
     * @param deviceStatus
     * @param device
     */
    private void updateLastWaitingEvent(DeviceStatus deviceStatus, DeviceEntity device){
        DeviceStatusTimeLineEntity lastOneWaiting = getLastOneInfoByStatus(device.getId(), MachineStatusEnum.WAITING.getCodeStr());

        //fixed（2021.11.11）：如果最近的waiting事件，已经标记了end_time，不需要重新再处理
        if(ObjectUtils.isEmpty(lastOneWaiting) || !ObjectUtils.isEmpty(lastOneWaiting.getEndTime())){
            //如果为空，不处理
            //...
            return;
        }

        //计算时间差
        Long leftSeconds = DateUtil.getLeftSeconds(lastOneWaiting.getStartTime(), deviceStatus.getCreateDate());
        lastOneWaiting.setTotalSpent(leftSeconds);

        //fixed（修复时序图展示缺失问题需要结束时间）：完善时序图结束时间  2021.11.10
        lastOneWaiting.setEndTime(deviceStatus.getCreateDate());
        lastOneWaiting.setEventTime(deviceStatus.getCreateDate());
        log.info("###########设备运行，更新当天最近一次status为 2 的时序记录：{}，记录主键为：{}",device.getName(), JSONObject.toJSONString(lastOneWaiting));
        //更新差值
        this.saveOrUpdate(lastOneWaiting);
    }

    /**
     * @desc 更新最近一条开机事件的开机时长
     * @param deviceStatus
     * @param device
     */
    public void updateLastTurningEvent(DeviceStatus deviceStatus, DeviceEntity device){

        //获取今天最近的一次开机事件记录
        DeviceStatusTimeLineEntity lastOneTURNING = getLastOneInfoByStatus(device.getId(), MachineStatusEnum.TURNING.getCodeStr());

        //fixed（2021.11.11）：如果最近的turning事件，已经标记了end_time，不需要重新再处理
        if(!ObjectUtils.isEmpty(lastOneTURNING) && ObjectUtils.isEmpty(lastOneTURNING.getEndTime())){
            //计算时间差
            Long leftSeconds = DateUtil.getLeftSeconds(lastOneTURNING.getStartTime(), deviceStatus.getCreateDate());
            lastOneTURNING.setTotalSpent(leftSeconds);

            //fixed（修复时序图展示缺失问题需要结束时间）：完善时序图结束时间  2021.11.10
            lastOneTURNING.setEndTime(deviceStatus.getCreateDate());
            lastOneTURNING.setEventTime(deviceStatus.getCreateDate());

            //更新差值
            this.saveOrUpdate(lastOneTURNING);
            log.info("###########设备停机，更新当天最近一次status为 3 的时序记录：{}，记录主键为：{}",device.getName(), JSONObject.toJSONString(lastOneTURNING));
        }

        //如果为空，今天认为异常，不处理
        //...
    }


    /**
     * @desc 新增设备
     * @param deviceStatus
     * @return
     */
    private DeviceEntity addDevice(DeviceStatus deviceStatus) {

        DeviceEntity deviceEntity = new DeviceEntity();
        deviceEntity.setBadCount(deviceStatus.getBadQuantity().longValue());
        deviceEntity.setGoodCount(deviceStatus.getGoodQuantity().longValue());

        //fixed(2021.12.07): 修复设备表中不能存在status为3的状态
        MachineStatusEnum runningStatus = deviceStatus.getRunningStatus();
        if(!ObjectUtils.isEmpty(runningStatus)){
            //fixed(2021.11.11): 设备表中不能存在状态为3
            deviceEntity.setStatus(runningStatus.getCodeStr());
        }

        deviceEntity.setTotalCount(deviceEntity.getGoodCount() + deviceEntity.getBadCount());

        deviceEntity.setCreatedTime(new Date().getTime());
        deviceEntity.setName(deviceStatus.getDeviceName());

        deviceService.saveOrUpdate(deviceEntity);

        devices.put(deviceEntity.getName(), deviceEntity.getId());

        return deviceEntity;
    }

    @Override
    public void reportStatusSplitFlow(String obj){
        JSONObject data = JSON.parseObject(obj);
        if(data == null || null == data.get("DeviceName") || StringUtils.isEmpty(data.get("DeviceName").toString())) return;
        try{
            Date date = new Date();
            String deviceName = data.get("DeviceName").toString();
            DeviceEntity device = deviceService.getDeviceByName(deviceName);
            if(device != null &&  data.get("Running") != null && !StringUtils.isEmpty(data.get("Running").toString())){
                //处理非时序状态数据
                JSONObject oeeObj = new JSONObject();
                oeeObj.put("DeviceName",data.get("DeviceName").toString());
                oeeObj.put("Running",data.get("Running").toString());
                oeeObj.put("Good Quantity",data.get("Good Quantity").toString());
                oeeObj.put("Bad Quantity",data.get("Bad Quantity").toString());
                rabbitSender.reportOeeParam(oeeObj);

                //处理时序状态数据
                JSONObject StatusLine = new JSONObject();
                StatusLine.put("Machines Online",data.get("Machines Online").toString());
                StatusLine.put("DeviceName",data.get("DeviceName").toString());
                StatusLine.put("Running",data.get("Running").toString());
                StatusLine.put("CreateDate",data.get("CreateDate").toString());
                rabbitSender.reportStatus(StatusLine);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public List<DeviceStatusTimeLineEntity> getListByDeviceId(String deviceId){
        List<DeviceStatusTimeLineEntity> list = deviceStatusTimeLineDao.getListByDeviceId(deviceId);
        return list;
    }


    @Override
    public DeviceStatusTimeLineEntity getLastOneInfoByDeviceId(String deviceId){
        DeviceStatusTimeLineEntity entity = deviceStatusTimeLineDao.getLastOneInfoByDeviceId(deviceId);
        return entity;
    }


    @Override
    public int getSumRunningTimeDay(String deviceId){
        //v2: 优化查询运行总时间sql  2021.11.08
        return  deviceStatusTimeLineDao.getSumRunningTimeDayV2(deviceId);
        //return deviceStatusTimeLineDao.getSumRunningTime(deviceId);
    }

    @Override
    public int getSumOpeningTimeDay(String deviceId) {
        return deviceStatusTimeLineDao.getSumROpeningTimeDayV2(deviceId);
    }


    @Override
    public Long getSumAllRunningTimeDay() {
        return deviceStatusTimeLineDao.getSumAllRunningTimeDayV2();
    }

    @Override
    public Long getSumAllOpeningTimeDay() {
        return deviceStatusTimeLineDao.getSumAllOpeningTimeDayV2();
    }

    @Override
    public int getSumTime(String deviceId){
        //v2: 优化查询运行总时间sql  2021.11.08
        return deviceStatusTimeLineDao.getSumTimeV2(deviceId);
        //return deviceStatusTimeLineDao.getSumTime(deviceId);
    }
}
